シンプルなアプリ状態管理
さて、あなたはそれについて知っています宣言型 UI プログラミングとの違い一時的なアプリの状態、 簡単なアプリの状態管理について学ぶ準備ができました。
このページでは、provider
パッケージ。
Flutter を初めて使用し、選択する強い理由がない場合
別のアプローチ (Redux、Rx、フックなど)、これがおそらくアプローチです
から始めるべきです。のprovider
パッケージがわかりやすい
コードはあまり使用しません。
また、他のすべてのアプローチに適用できる概念も使用します。
そうは言っても、あなたに強力なバックグラウンドがある場合、 他のリアクティブフレームワークからの状態管理、 パッケージとチュートリアルは次の場所にリストされています。オプションページ。
私たちの例
説明のために、次の単純なアプリを考えてみましょう。
アプリには、カタログ、
とカート (で表されます)MyCatalog
、
とMyCart
それぞれウィジェット)。ショッピングアプリかもしれませんが、
しかし、単純なソーシャル ネットワーキングでも同じ構造を想像できます。
アプリ (カタログを「ウォール」に、カートを「お気に入り」に置き換えます)。
カタログ画面にはカスタム アプリ バー (MyAppBar
)
多くのリスト項目のスクロールビュー (MyListItems
)。
これはアプリをウィジェット ツリーとして視覚化したものです。
したがって、少なくとも 5 つのサブクラスがあります。Widget
。彼らの多くは必要とする
他の場所に「属する」状態へのアクセス。たとえば、それぞれMyListItem
自身をカートに追加できる必要があります。
また、現在表示されている項目が
はすでにカートに入っています。
これにより、最初の質問が始まります。現在のものをどこに置くべきかということです。 カートの状態は?
状態を引き上げる
flutterでは、 状態を使用するウィジェットの上に状態を保持することは理にかなっています。
なぜ? Flutterのような宣言型フレームワークでUIを変更したい場合は、
それを再構築する必要があります。簡単に手に入れる方法はないMyCart.updateWith(somethingNew)
。言い換えれば、難しいのは、
ウィジェットのメソッドを呼び出すことで、外部からウィジェットを強制的に変更します。
たとえこれがうまくいったとしても、あなたは敵と戦うことになるでしょう。
フレームワークを役立つようにするのではなく、フレームワークを使用します。
// BAD: DO NOT DO THIS
void myTapHandler() {
var cartWidget = somehowGetMyCartWidget();
cartWidget.updateWith(item);
}
上記のコードが動作したとしても、
そうすればあなたは対処しなければならないでしょう
に次のものがありますMyCart
ウィジェット:
// BAD: DO NOT DO THIS
Widget build(BuildContext context) {
return SomeWidget(
// The initial state of the cart.
);
}
void updateWith(Item item) {
// Somehow you need to change the UI from here.
}
UI の現在の状態を考慮する必要があります。 新しいデータをそれに適用します。この方法でバグを回避するのは困難です。
Flutter では、内容が変更されるたびに新しいウィジェットを構築します。
それ以外のMyCart.updateWith(somethingNew)
(メソッド呼び出し)
あなたが使うMyCart(contents)
(コンストラクター)。あなたにしかできないから
親のビルドメソッドで新しいウィジェットを構築します。
変わりたいならcontents
に住む必要があります。MyCart
の
親以上。
// GOOD
void myTapHandler(BuildContext context) {
var cartModel = somehowGetMyCartModel(context);
cartModel.add(item);
}
今MyCart
には、任意のバージョンの UI を構築するためのコード パスが 1 つだけあります。
// GOOD
Widget build(BuildContext context) {
var cartModel = somehowGetMyCartModel(context);
return SomeWidget(
// Just construct the UI once, using the current state of the cart.
// ···
);
}
私たちの例では、contents
に住む必要があるMyApp
。変化するたびに、
それは再構築しますMyCart
上から(詳細は後ほど)。このため、MyCart
ライフサイクルを気にする必要はありません。宣言するだけです。
与えられたものに対して何を示すかcontents
。それが変わると、古いものはMyCart
ウィジェットが消え、新しいウィジェットに完全に置き換えられます。
これが、ウィジェットが不変であると言うときの意味です。 それらは変わるのではなく、置き換えられるのです。
カートの状態をどこに置くかがわかったので、その方法を見てみましょう。 アクセスするには。
状態へのアクセス
ユーザーがカタログ内のアイテムの 1 つをクリックすると、
カートに追加されます。でもカートは上にあるのでMyListItem
、
どうすればいいでしょうか?
簡単なオプションは、次のようなコールバックを提供することです。MyListItem
呼び出すことができます
クリックされたとき。 Dart の関数はファーストクラスのオブジェクトです。
好きなように渡すことができます。それで、中ではMyCatalog
次のように定義できます。
@override
Widget build(BuildContext context) {
return SomeWidget(
// Construct the widget, passing it a reference to the method above.
MyListItem(myTapCallback),
);
}
void myTapCallback(Item item) {
print('user tapped on $item');
}
これは問題なく動作しますが、アプリの状態を変更する必要がある場合は、 さまざまな場所にあるため、多くの場所を通過する必要があります コールバック - これはすぐに古くなってしまいます。
幸いなことに、Flutter にはウィジェットがデータを提供するメカニズムがあり、
子孫への奉仕(言い換えれば、子供たちだけでなく、
ただし、その下のウィジェットはすべて含まれます)。ご想像のとおり、Flutter では、
どこすべてはウィジェット™、これらのメカニズムは単に特別です
ウィジェットの種類—InheritedWidget
、InheritedNotifier
、InheritedModel
、 もっと。ここではそれらについては取り上げませんが、
なぜなら、それらは私たちがやろうとしていることに対して少し低レベルだからです。
代わりに、低レベルで動作するパッケージを使用します。
ウィジェットですが使い方は簡単です。それは呼ばれていますprovider
。
作業する前にprovider
、
それへの依存関係を忘れずに追加してくださいpubspec.yaml
。
追加するには、provider
パッケージを依存関係として実行しますflutter pub add
:
$ flutter pub add provider
今ならできるimport 'package:provider/provider.dart';
そして構築を開始します。
とprovider
、コールバックやInheritedWidgets
。ただし、次の 3 つの概念を理解する必要があります。
- ChangeNotifier
- ChangeNotifierProvider
- 消費者
ChangeNotifier
ChangeNotifier
Flutter SDK に含まれる単純なクラスです。
リスナーへの変更通知。言い換えれば、何かがあれば、
あるChangeNotifier
、その変更を購読できます。 (の形です
この用語に精通している人にとっては観察可能です。)
のprovider
、ChangeNotifier
アプリケーションをカプセル化する 1 つの方法です
州。非常に単純なアプリの場合は、1 つだけで済みます。ChangeNotifier
。
複雑なモデルでは、複数のモデルが存在するため、複数のモデルが存在します。ChangeNotifiers
。 (使用する必要はありませんChangeNotifier
とprovider
全く問題ありませんが、扱いやすいクラスです。)
ショッピング アプリの例では、カートの状態を管理したいと考えています。ChangeNotifier
。次のように、それを拡張する新しいクラスを作成します。
class CartModel extends ChangeNotifier {
/// Internal, private state of the cart.
final List<Item> _items = [];
/// An unmodifiable view of the items in the cart.
UnmodifiableListView<Item> get items => UnmodifiableListView(_items);
/// The current total price of all items (assuming all items cost $42).
int get totalPrice => _items.length * 42;
/// Adds [item] to cart. This and [removeAll] are the only ways to modify the
/// cart from the outside.
void add(Item item) {
_items.add(item);
// This call tells the widgets that are listening to this model to rebuild.
notifyListeners();
}
/// Removes all items from the cart.
void removeAll() {
_items.clear();
// This call tells the widgets that are listening to this model to rebuild.
notifyListeners();
}
}
に固有の唯一のコードChangeNotifier
電話です
にnotifyListeners()
。モデルが何らかの方法で変更されるたびにこのメソッドを呼び出します
アプリの UI が変更される可能性があります。それ以外はすべてCartModel
それは
モデル自体とそのビジネス ロジック。
ChangeNotifier
の一部ですflutter:foundation
そして依存しない
Flutter の上位レベルのクラス。簡単にテストできます(テストする必要すらありません)
使用するウィジェットのテストそれのための)。例えば、
これは簡単な単体テストですCartModel
:
test('adding item increases total cost', () {
final cart = CartModel();
final startingPrice = cart.totalPrice;
var i = 0;
cart.addListener(() {
expect(cart.totalPrice, greaterThan(startingPrice));
i++;
});
cart.add(Item('Dash'));
expect(i, 1);
});
ChangeNotifierProvider
ChangeNotifierProvider
のインスタンスを提供するウィジェットです
あるChangeNotifier
その子孫に。それはから来ていますprovider
パッケージ。
どこに置くかはすでにわかっていますChangeNotifierProvider
: ウィジェットの上にある
それにアクセスする必要があります。の場合CartModel
、それはどこかを意味します
両方の上にMyCart
とMyCatalog
。
置きたくないChangeNotifierProvider
必要以上に高い
(スコープを汚したくないため)。しかし、私たちの場合、
両方の上にある唯一のウィジェットMyCart
とMyCatalog
はMyApp
。
void main() {
runApp(
ChangeNotifierProvider(
create: (context) => CartModel(),
child: const MyApp(),
),
);
}
新しいインスタンスを作成するビルダーを定義していることに注意してください。
のCartModel
。ChangeNotifierProvider
十分賢いですいいえ再構築するCartModel
絶対に必要な場合を除きます。また、自動的に電話をかけますdispose()
の上CartModel
インスタンスが不要になったとき。
複数のクラスを提供したい場合は、次のように使用できます。MultiProvider
:
void main() {
runApp(
MultiProvider(
providers: [
ChangeNotifierProvider(create: (context) => CartModel()),
Provider(create: (context) => SomeOtherClass()),
],
child: const MyApp(),
),
);
}
消費者
今CartModel
を通じてアプリ内のウィジェットに提供されます。ChangeNotifierProvider
上部で宣言すると、使用を開始できます。
これは、Consumer
ウィジェット。
return Consumer<CartModel>(
builder: (context, cart, child) {
return Text('Total price: ${cart.totalPrice}');
},
);
アクセスしたいモデルのタイプを指定する必要があります。
この場合、私たちが望むのは、CartModel
、ということで書きますConsumer<CartModel>
。ジェネリックを指定しない場合 (<CartModel>
)、
のprovider
パッケージはあなたを助けることができません。provider
タイプに基づいており、
型がなければ、ユーザーが何を望んでいるのかわかりません。
の唯一の必須引数Consumer
ウィジェット
ビルダーです。 Builder は、ChangeNotifier
変化します。 (つまり、電話をかけると、notifyListeners()
モデル内の、対応するすべてのビルダー メソッドConsumer
ウィジェットが呼び出されます。)
ビルダーは 3 つの引数を指定して呼び出されます。一つ目はcontext
、
これはすべてのビルド メソッドでも得られます。
ビルダー関数の 2 番目の引数は、次のインスタンスです。
のChangeNotifier
。それが私たちが最初に求めていたものです。
モデル内のデータを使用して、UI がどのように見えるかを定義できます。
いつでも。
3 番目の引数はchild
、最適化のためにあります。
大きなウィジェットのサブツリーが下にある場合は、Consumer
それかしませんモデルが変更されると変更され、それを構築できます
一度ビルダーを通して取得してください。
return Consumer<CartModel>(
builder: (context, cart, child) => Stack(
children: [
// Use SomeExpensiveWidget here, without rebuilding every time.
if (child != null) child,
Text('Total price: ${cart.totalPrice}'),
],
),
// Build the expensive widget here.
child: const SomeExpensiveWidget(),
);
ベストプラクティスとして、Consumer
ツリーの奥深くにあるウィジェット
できるだけ。 UI の大部分を再構築したくない場合
どこかの詳細が変更されただけです。
// DON'T DO THIS
return Consumer<CartModel>(
builder: (context, cart, child) {
return HumongousWidget(
// ...
child: AnotherMonstrousWidget(
// ...
child: Text('Total price: ${cart.totalPrice}'),
),
);
},
);
その代わり:
// DO THIS
return HumongousWidget(
// ...
child: AnotherMonstrousWidget(
// ...
child: Consumer<CartModel>(
builder: (context, cart, child) {
return Text('Total price: ${cart.totalPrice}');
},
),
),
);
プロバイダーの
場合によっては、実際には必要ない場合もありますデータ変更するモデル内で
UI ですが、アクセスする必要があります。たとえば、ClearCart
ボタンは、ユーザーがカートからすべてを削除できるようにしたいと考えています。
カートの中身を表示する必要はありませんが、
を呼び出すだけですclear()
方法。
使えますConsumer<CartModel>
このため、
しかしそれは無駄です。私たちがフレームワークに求めるのは、
再構築する必要のないウィジェットを再構築します。
このユースケースでは、次を使用できますProvider.of
、
とともにlisten
パラメータを次のように設定false
。
Provider.of<CartModel>(context, listen: false).removeAll();
ビルド メソッドで上記の行を使用しても、このウィジェットは
いつ再構築するかnotifyListeners
と呼ばれます。
すべてを一緒に入れて
あなたはできる例をチェックしてくださいこの記事で説明します。
もっとシンプルなものが必要な場合は、
シンプルなカウンター アプリがどのように表示されるかを確認してください。で構築されたprovider
。
これらの記事に従うことで、大幅な効果が得られます
状態ベースのアプリケーションを作成する能力が向上しました。
でアプリケーションを構築してみてくださいprovider
自分自身に
これらのスキルをマスターしてください。